import { Renderer } from './Renderer';
import * as DomUtil from '../../dom/DomUtil';
import * as DomEvent from '../../dom/DomEvent';
import Browser from '../../core/Browser';
import { stamp } from '../../core/Util';
import { svgCreate, pointsToPath } from './SVG.Util';
export { pointsToPath };
import { vmlMixin, vmlCreate } from './SVG.VML';

export var create = Browser.vml ? vmlCreate : svgCreate;

/*
 * @class SVG
 * @inherits Renderer
 * @aka L.SVG
 *
 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
 * Inherits `Renderer`.
 *
 * Due to [technical limitations](https://caniuse.com/svg), SVG is not
 * available in all web browsers, notably Android 2.x and 3.x.
 *
 * Although SVG is not available on IE7 and IE8, these browsers support
 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
 * (a now deprecated technology), and the SVG renderer will fall back to VML in
 * this case.
 *
 * @example
 *
 * Use SVG by default for all paths in the map:
 *
 * ```js
 * var map = L.map('map', {
 * 	renderer: L.svg()
 * });
 * ```
 *
 * Use a SVG renderer with extra padding for specific vector geometries:
 *
 * ```js
 * var map = L.map('map');
 * var myRenderer = L.svg({ padding: 0.5 });
 * var line = L.polyline( coordinates, { renderer: myRenderer } );
 * var circle = L.circle( center, { renderer: myRenderer } );
 * ```
 */

export var SVG = Renderer.extend({
  _initContainer: function () {
    this._container = create('svg');

    // makes it possible to click through svg root; we'll reset it back in individual paths
    this._container.setAttribute('pointer-events', 'none');

    this._rootGroup = create('g');
    this._container.appendChild(this._rootGroup);
  },

  _destroyContainer: function () {
    DomUtil.remove(this._container);
    DomEvent.off(this._container);
    delete this._container;
    delete this._rootGroup;
    delete this._svgSize;
  },

  _update: function () {
    if (this._map._animatingZoom && this._bounds) {
      return;
    }

    Renderer.prototype._update.call(this);

    var b = this._bounds,
      size = b.getSize(),
      container = this._container;

    // set size of svg-container if changed
    if (!this._svgSize || !this._svgSize.equals(size)) {
      this._svgSize = size;
      container.setAttribute('width', size.x);
      container.setAttribute('height', size.y);
    }

    // movement: update container viewBox so that we don't have to change coordinates of individual layers
    DomUtil.setPosition(container, b.min);
    container.setAttribute(
      'viewBox',
      [b.min.x, b.min.y, size.x, size.y].join(' '),
    );

    this.fire('update');
  },

  // methods below are called by vector layers implementations

  _initPath: function (layer) {
    var path = (layer._path = create('path'));

    // @namespace Path
    // @option className: String = null
    // Custom class name set on an element. Only for SVG renderer.
    if (layer.options.className) {
      DomUtil.addClass(path, layer.options.className);
    }

    if (layer.options.interactive) {
      DomUtil.addClass(path, 'leaflet-interactive');
    }

    this._updateStyle(layer);
    this._layers[stamp(layer)] = layer;
  },

  _addPath: function (layer) {
    if (!this._rootGroup) {
      this._initContainer();
    }
    this._rootGroup.appendChild(layer._path);
    layer.addInteractiveTarget(layer._path);
  },

  _removePath: function (layer) {
    DomUtil.remove(layer._path);
    layer.removeInteractiveTarget(layer._path);
    delete this._layers[stamp(layer)];
  },

  _updatePath: function (layer) {
    layer._project();
    layer._update();
  },

  _updateStyle: function (layer) {
    var path = layer._path,
      options = layer.options;

    if (!path) {
      return;
    }

    if (options.stroke) {
      path.setAttribute('stroke', options.color);
      path.setAttribute('stroke-opacity', options.opacity);
      path.setAttribute('stroke-width', options.weight);
      path.setAttribute('stroke-linecap', options.lineCap);
      path.setAttribute('stroke-linejoin', options.lineJoin);

      if (options.dashArray) {
        path.setAttribute('stroke-dasharray', options.dashArray);
      } else {
        path.removeAttribute('stroke-dasharray');
      }

      if (options.dashOffset) {
        path.setAttribute('stroke-dashoffset', options.dashOffset);
      } else {
        path.removeAttribute('stroke-dashoffset');
      }
    } else {
      path.setAttribute('stroke', 'none');
    }

    if (options.fill) {
      path.setAttribute('fill', options.fillColor || options.color);
      path.setAttribute('fill-opacity', options.fillOpacity);
      path.setAttribute('fill-rule', options.fillRule || 'evenodd');
    } else {
      path.setAttribute('fill', 'none');
    }
  },

  _updatePoly: function (layer, closed) {
    this._setPath(layer, pointsToPath(layer._parts, closed));
  },

  _updateCircle: function (layer) {
    var p = layer._point,
      r = Math.max(Math.round(layer._radius), 1),
      r2 = Math.max(Math.round(layer._radiusY), 1) || r,
      arc = 'a' + r + ',' + r2 + ' 0 1,0 ';

    // drawing a circle with two half-arcs
    var d = layer._empty()
      ? 'M0 0'
      : 'M' +
        (p.x - r) +
        ',' +
        p.y +
        arc +
        r * 2 +
        ',0 ' +
        arc +
        -r * 2 +
        ',0 ';

    this._setPath(layer, d);
  },

  _setPath: function (layer, path) {
    layer._path.setAttribute('d', path);
  },

  // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
  _bringToFront: function (layer) {
    DomUtil.toFront(layer._path);
  },

  _bringToBack: function (layer) {
    DomUtil.toBack(layer._path);
  },
});

if (Browser.vml) {
  SVG.include(vmlMixin);
}

// @namespace SVG
// @factory L.svg(options?: Renderer options)
// Creates a SVG renderer with the given options.
export function svg(options) {
  return Browser.svg || Browser.vml ? new SVG(options) : null;
}
